/*
 * Copyright 2008-2009 Daniel Cachapa <cachapa@gmail.com>
 * 
 * This program is distributed under the terms of the GNU General Public Licence Version 3
 * The licence can be read in its entirety in the LICENSE.txt file accompaning this source code,
 * or at: http://www.gnu.org/copyleft/gpl.html
 * 
 * This file is part of WeightWatch.
 *
 * WeightWatch is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 *
 * WeightWatch is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the WeightWatch source code. If not, see
 * <http://www.gnu.org/licenses/>.
 */

package net.cachapa.weightwatch.ui;

import com.sun.lwuit.Command;
import com.sun.lwuit.Container;
import com.sun.lwuit.Dialog;
import com.sun.lwuit.Form;
import com.sun.lwuit.Label;
import com.sun.lwuit.events.ActionEvent;
import com.sun.lwuit.events.ActionListener;
import com.sun.lwuit.events.DataChangedListener;
import com.sun.lwuit.layouts.BoxLayout;
import net.cachapa.weightwatch.Bmi;
import net.cachapa.weightwatch.util.MyDate;
import net.cachapa.weightwatch.Plan;
import net.cachapa.weightwatch.Value;
import net.cachapa.weightwatch.ui.components.DateField;
import net.cachapa.weightwatch.ui.components.NumericField;

public class PlanForm extends Form implements ActionListener {
	private Application application;
	private DateField startDateField;
	private NumericField initialWeightField;
	private NumericField goalWeightField;
	private NumericField weeklyChangeField;
	private NumericField dailyBalanceField;
	private DateField endDateField;
	private Command acceptCommand;
	private Command suggestValuesCommand;
	private Command backCommand;
	private Plan plan;
	private boolean updatingValues = false;	// used to avoid deadlocks due to onChange events when updating the components

	public PlanForm(Application application) {
		this.application = application;
		this.setTitle("plan_title");
		this.setLayout(new BoxLayout(BoxLayout.Y_AXIS));
		
		// Starting date
		Container startDateContainer = new Container(new BoxLayout(BoxLayout.X_AXIS));
		startDateContainer.addComponent(new Label("plan_start"));
		startDateField = new DateField(this);
		startDateField.addDataChangeListener(new DataChangedListener() {
			public void dataChanged(int arg0, int arg1) {
				startDateChanged();
			}
		});
		startDateContainer.addComponent(startDateField);
		this.addComponent(startDateContainer);
		
		// Initial weight
		Container initialWeightContainer = new Container(new BoxLayout(BoxLayout.X_AXIS));
		initialWeightContainer.addComponent(new Label("plan_initialweight"));
		initialWeightField = new NumericField(6);
		initialWeightField.setUpDownValueChange(0.1);
		initialWeightField.addDataChangeListener(new DataChangedListener() {
			public void dataChanged(int arg0, int arg1) {
				initialWeightChanged();
			}
		});
		initialWeightContainer.addComponent(initialWeightField);
		this.addComponent(initialWeightContainer);
		
		// Goal weight
		Container goalWeightContainer = new Container(new BoxLayout(BoxLayout.X_AXIS));
		goalWeightContainer.addComponent(new Label("plan_goalweight"));
		goalWeightField = new NumericField(6);
		goalWeightField.setUpDownValueChange(0.1);
		goalWeightField.addDataChangeListener(new DataChangedListener() {
			public void dataChanged(int arg0, int arg1) {
				goalWeightChanged();
			}
		});
		goalWeightContainer.addComponent(goalWeightField);
		this.addComponent(goalWeightContainer);
		
		// Weekly change
		Container weeklyChangeContainer = new Container(new BoxLayout(BoxLayout.X_AXIS));
		if (application.getWeightWatch().getPreferences().isMetricUnits()) {
			weeklyChangeContainer.addComponent(new Label("plan_change_metric"));
		}
		else {
			weeklyChangeContainer.addComponent(new Label("plan_change_imperial"));
		}
		weeklyChangeField = new NumericField(6);
		weeklyChangeField.setUpDownValueChange(0.01);
		weeklyChangeField.addDataChangeListener(new DataChangedListener() {
			public void dataChanged(int arg0, int arg1) {
				weeklyChangeChanged();
			}
		});
		weeklyChangeContainer.addComponent(weeklyChangeField);
		this.addComponent(weeklyChangeContainer);
		
		// Daily balance
		Container dailyBalanceContainer = new Container(new BoxLayout(BoxLayout.X_AXIS));
		dailyBalanceContainer.addComponent(new Label("plan_dailybalance"));
		dailyBalanceField = new NumericField(6);
		dailyBalanceField.setUpDownValueChange(1);
		dailyBalanceField.addDataChangeListener(new DataChangedListener() {
			public void dataChanged(int arg0, int arg1) {
				dailyBalanceChanged();
			}
		});
		dailyBalanceContainer.addComponent(dailyBalanceField);
		this.addComponent(dailyBalanceContainer);
		
		// End date
		Container endDateContainer = new Container(new BoxLayout(BoxLayout.X_AXIS));
		endDateContainer.addComponent(new Label("plan_enddate"));
		endDateField = new DateField(this);
		endDateField.addDataChangeListener(new DataChangedListener() {
			public void dataChanged(int arg0, int arg1) {
				endDateChanged();
			}
		});
		endDateContainer.addComponent(endDateField);
		this.addComponent(endDateContainer);
				
		acceptCommand = new Command("accept");
		suggestValuesCommand = new Command("suggestvalues");
		backCommand = new Command("back");
		this.addCommand(acceptCommand);
		this.addCommand(suggestValuesCommand);
		this.addCommand(backCommand);
		this.setBackCommand(backCommand);
		this.setCommandListener(this);
	}
	
	public void show() {
		plan = new Plan(application.getWeightWatch().getPlan());
		updatingValues = true;
		startDateField.setTime(plan.getStartDate().getTime());
		initialWeightField.setValue(plan.getStartWeight());
		goalWeightField.setValue(plan.getGoalWeight());
		weeklyChangeField.setValue(plan.getWeeklyChange());
		dailyBalanceField.setValue(plan.getDailyBalance());
		endDateField.setTime(plan.getEndDate().getTime());
		updatingValues = false;
		super.show();
	}
	
	public void actionPerformed(ActionEvent ae) {
		Object source = ae.getSource();
		if (source == acceptCommand) {
			// Save values
			MyDate startDate = new MyDate(startDateField.getTime());
			double startWeight = initialWeightField.getValue();
			double goalWeight = goalWeightField.getValue();
			int dailyBalance = (int)dailyBalanceField.getValue();
			Plan replacementPlan = new Plan(startDate, startWeight, goalWeight, dailyBalance);
			application.getWeightWatch().getPreferences().setPlan(replacementPlan);
			
			// Go to preferences form
			application.performAction(Application.SHOW_PREFERENCES_FORM);
		}
		else if (source == suggestValuesCommand) {
			// Try to autofill
			autoFillValues();
		}
		else if (source == backCommand) {
			// Go to preferences form
			application.performAction(Application.SHOW_PREFERENCES_FORM);
		}
	}

	private void startDateChanged() {
		if (updatingValues) return;
		updatingValues = true;
		plan.setStartDate(new MyDate(startDateField.getTime()));
		endDateField.setTime(plan.getEndDate().getTime());
		updatingValues = false;
	}
	
	private void initialWeightChanged() {
		if (updatingValues) return;
		updatingValues = true;
		plan.setStartWeight(initialWeightField.getValue());
		endDateField.setTime(plan.getEndDate().getTime());
		updatingValues = false;
	}
	
	private void goalWeightChanged() {
		if (updatingValues) return;
		updatingValues = true;
		plan.setGoalWeight(goalWeightField.getValue());
		endDateField.setTime(plan.getEndDate().getTime());
		updatingValues = false;
	}
	
	private void weeklyChangeChanged() {
		if (updatingValues) return;
		updatingValues = true;
		plan.setWeeklyChange(weeklyChangeField.getValue());
		dailyBalanceField.setValue(plan.getDailyBalance());
		endDateField.setTime(plan.getEndDate().getTime());
		updatingValues = false;
	}
	
	private void dailyBalanceChanged() {
		if (updatingValues) return;
		updatingValues = true;
		plan.setDailyBalance((int)dailyBalanceField.getValue());
		weeklyChangeField.setValue(plan.getWeeklyChange());
		endDateField.setTime(plan.getEndDate().getTime());
		updatingValues = false;
	}
	
	private void endDateChanged() {
		if (updatingValues) return;
		updatingValues = true;
		plan.setEndDate(new MyDate(endDateField.getTime()));
		weeklyChangeField.setValue(plan.getWeeklyChange());
		dailyBalanceField.setValue(plan.getDailyBalance());
		updatingValues = false;
	}
	
	private void autoFillValues() {
		// 1. Checks that there it at least one value in the database, and that the
		// user already inserted the height. If one of these are false, show an error
		// dialog
		Value latestValue = application.getWeightWatch().getDatabase().getLatestValue();
		double userHeight = application.getWeightWatch().getPreferences().getUserHeight();
		if (latestValue.weight == 0 || userHeight == 0) {
			Dialog.show("error", "plan_heightweightmissing",
				new Command[] {new Command("ok")}, Dialog.TYPE_ERROR, null, 0);
			return;
		}
		
		// 2. Use the latest values to fill the starting date and weight fields
		startDateField.setTime(latestValue.date.getTime());
		initialWeightField.setValue(latestValue.weight);
		
		// 3. Calculate the BMI value that will fall directly in the middle of the
		// Normal zone and use it to fill the goal weight field
		double goalWeight = Bmi.getAverageWeight(userHeight, application.getWeightWatch().getPreferences().isMetricUnits());
		goalWeight = (double)(int)(goalWeight * 10) / 10;
		goalWeightField.setValue(goalWeight);
		
		// 4. Check if the user needs to gain or lose weight and fill the calories
		// field with +-700 KCals (a decent, average value)
		if (latestValue.weight < goalWeight) {
			dailyBalanceField.setValue(700);
		} else {
			dailyBalanceField.setValue(-700);
		}
	}
}
